#! /bin/bash
#
# @configure_input@ 
# on @DATE@
#

# Basic variables
export LANG=C
prefix=@prefix@
exec_prefix=@exec_prefix@
RTAPI_INI=@EMC2_SYSTEM_CONFIG_DIR@/rtapi.ini
INIVAR=@EMC2_LIBEXEC_DIR@/inivar
LSMOD=@LSMOD@
PIDOF=@PIDOF@
PS=@PS@
AWK=@AWK@
SHM_DEV=/dev/shmdrv
SHM_STAT=/sys/devices/virtual/misc/shmdrv/status

# if not overridden by user, point MACHINEKIT_INI to installed default:
if [ -z "$MACHINEKIT_INI" ]; then
    MACHINEKIT_INI=@EMC2_SYSTEM_CONFIG_DIR@/machinekit.ini
fi
export MACHINEKIT_INI

# Basic checks
if ! test -r "$RTAPI_INI"; then
    echo "RTAPI configuration file unreadable:  $RTAPI_INI" >&2
    exit 1
fi
if ! test -x "$INIVAR"; then
    echo "Config file tool unusable:  $INIVAR" >&2
    exit 1
fi

# Helper function to get variables from config
getvar() {
    # getvar <variable> [ --flavor ]
    # set variable from rtapi_ini;
    # with --flavor, if $FLAVOR is set, look in the [flavor_$FLAVOR] section
    var=$1
    flav_var=$2

    if test "$flav_var" = "--flavor" -a ! -z "$FLAVOR"; then
	SEC="-sec flavor_$FLAVOR"
    else
	SEC=""
    fi

    eval "$var='$($INIVAR -var $var -ini $RTAPI_INI $SEC 2>/dev/null)'"
}

# Fill in other variable values
#
# Executables
getvar flavor
getvar linuxcnc_module_helper
getvar rtapi_msgd
#getvar halcmd

# Flavor configuration
FLAVOR=$($flavor)
BUILD_SYS=`${flavor} -b`
getvar MODULES --flavor
getvar rtapi_app --flavor

# catch this common error early on:
if ! test -u ${rtapi_app}
then
    echo Warning - ${rtapi_app} not setuid >&2
    echo "'sudo make setuid' missing?" >&2
fi

# Other, overridable by environment variables
if [ "$USE_SHMDRV" == "" ]  ; then
    getvar USE_SHMDRV
fi

if [ "$HAL_SIZE" == "" ]  ; then
    getvar HAL_SIZE
fi

if [ "$DEBUG" == "" ]  ; then
    getvar DEBUG
fi
DEBUG=$((10#0$DEBUG))  # be sure $DEBUG is some base-10 number

# SHMDRV_OPTS:
# suggested use: SHMDRV_OPTS='debug=3'
# which will log shmdrv operations to the kernel log
if [ "$SHMDRV_OPTS" != "" ]  ; then
    MSGD_OPTS="$MSGD_OPTS --shmdrv_opts=$SHMDRV_OPTS"
fi

# SYSLOG_TO_STDERR:  when set, log to stdout instead of syslog
if test -n "$SYSLOG_TO_STDERR"; then
    MSGD_OPTS+=" -s"
    RTAPI_APP_OPTS+=" -s"
fi

# set the default instance, if not already set
MK_INSTANCE=`printf '%d' $MK_INSTANCE`
if [ "$MK_INSTANCE_NAME" != "" ]  ; then
    NAME_CMD=--instance_name="$MK_INSTANCE_NAME"
else
    unset NAME_CMD
fi


# Compute module lists
#
# kernel threads also need the rtapi and hal_lib kernel modules
test $BUILD_SYS = kbuild && MODULES="$MODULES rtapi hal_lib"
#
# reverse list of modules for unloading
MODULES_UNLOAD=
for MOD in $MODULES; do
    MODULES_UNLOAD="$MOD $MODULES_UNLOAD"
done



# wait for a process to exit
anywait(){
    proc_name=$1
    tries=${2:-20}
    sig=${3:-0}
    pid=$(FindRunningProcs $proc_name)
    if test -z "$pid"; then
	return 0 # Already exited
    fi

    # Kill proc if requested
    if test -n "$sig"; then
	kill -$sig "$pid" >/dev/null 2>&1 || return 0
    fi

    # Wait to see if it exited
    for (( n=0; $n<$tries; $((n++)) )); do
	# return success if process died
	if ! CheckRunningProcs $proc_name; then
	    return 0
	fi
	# otherwise, wait 1/2 second and try again, up to $tries/2 seconds
	sleep 0.5
    done
    return 1
}

FindRunningProcs() {
    # Usage: FindRunningProcs $proc_name
    # Find non-zombie procs named $proc_name and echo their PIDs
    local proc_name=$1
    local all="$(${PIDOF} ${proc_name})"
    if test -z "$all"; then
	return  # No procs named $proc_name found at all
    fi
    local live="$(ps -p "$all" -o pid= -o s= | ${AWK} '$2 != "Z" {print $1}')"
    if test -z "$live"; then
	return  # No non-zombie procs named $proc_name found
    fi
    echo $live
    return 0
}

CheckRunningProcs() {
    # Usage: CheckRunningProcs $proc_name
    # Return true if there are any non-zombie procs named $proc_name, else false
    local proc_name=$1
    local live="$(FindRunningProcs "$proc_name")"
    test -n "$live"
}

CheckStatus(){
    local res=0

    # check if rtapi_msgd and rtapi_app are running
    progs="msgd:${MK_INSTANCE} rtapi:${MK_INSTANCE}"

    for prog in $progs; do
	if CheckRunningProcs $prog; then
	    echo "$prog running" >&2
	else
	    echo "$prog stopped" >&2
	    res=1
	fi
    done

    if test $BUILD_SYS = kbuild; then
        # check loaded/unloaded status of modules
        for MOD in $MODULES_UNLOAD ; do
            if $LSMOD | awk '{print $1}' | grep -x $MOD >/dev/null ; then
                echo "$MOD loaded" >&2
            else
                echo "$MOD not loaded" >&2
                res=1
            fi
        done
    fi

    # check if shmdrv is loaded, when applicable
    if test $BUILD_SYS = 'kbuild' -o $USE_SHMDRV = yes; then
	if CheckShm -l; then
	    echo "shmdrv loaded" >&2
	else
	    echo "shmdrv not loaded" >&2
	    res=1
	fi
    fi

    return $res
}


Load(){

    # kernel threads must have shmdrv.ko loaded
    # userland threads optionally may use the shmdrv module.
    # The shmdrv module is loaded by rtapi_msgd if needed.

    # rtapi_msgd creates the global segment containing the error ring buffer
    # so start this first:

    local cmd=(${rtapi_msgd} --instance=$MK_INSTANCE $NAME_CMD
			     --rtmsglevel=$DEBUG
			     --usrmsglevel=$DEBUG
                 --debug=$DEBUG
			     --halsize=$HAL_SIZE $MSGD_OPTS)
    [ $DEBUG -eq 0 ] || echo "rtapi_msgd command:  ${cmd[@]}" >&2
    "${cmd[@]}" || (
	e=$?; echo "rtapi_msgd startup failed - aborting" >&2; exit $e)

    # rtapi_app_<flavor> now handles the kernel module loading
    # for kthreads as needed
    local cmd=(${rtapi_app} --instance=$MK_INSTANCE --debug=$DEBUG $RTAPI_APP_OPTS)
    if [ $DEBUG -gt 0 ] ; then
	echo "rtapi_app command:  ${cmd[@]}" >&2
	"${cmd[@]}" || (
	    e=$?; echo "rtapi_app startup failed; aborting" >&2; exit $e)
    else
	"${cmd[@]}" 2>&1 || (
	    e=$?; echo "rtapi_app startup failed; aborting" >&2; exit $e)
    fi
    # wait until rtapi_app responds, meaning setup is complete
    # this avoids startup races
    halcmd ping

    if test BUILD_SYS = 'kbuild'; then
	# help debugging startup issues for kthreads
        if [ $DEBUG -gt 0 ] && [ -w /proc/rtapi/debug ] ; then
            echo "$DEBUG" > /proc/rtapi/debug
        fi
    fi
}

CheckShm(){
    # -l: return success if shmdrv device exists
    # -w: wait for shmdrv to be loaded; return success if loaded

    # Skip checks if shmdrv is not applicable
    test $BUILD_SYS = 'user-dso' -a $USE_SHMDRV = no && return 0

    case "$1" in
	-l)
	    # success if shmdrv device exists
	    test -c $SHM_DEV; return ;;

	-w)
	    # wait for shmdrv to be loaded; this abomination is needed
	    # because udev sometimes doesn't have the device ready for
	    # us in time.
	    n=0
	    while [ $((n++)) -lt 100 ]; do
		[ -w $SHM_DEV ] && return 0
		sleep .1
	    done

	    # $SHM_DEV never became writable after ten seconds
	    return 1;;
    esac
}



RemoveModules(){
    # Remove a list of modules recursively
    # 
    # When RTAPI shuts down uncleanly, not only hal_lib and rtapi may
    # still be loaded, but also comp, motmod, or other modules that
    # depend on those.
    #
    # Check for loaded modules dependent on hal_lib and unload them
    # first.

    for MODULE in $*; do
        # recurse on any dependent modules in /proc/modules
        DEP_MODULES=$(cat /proc/modules | \
            awk '/^'$MODULE' / { mods=$4; gsub(","," ",mods); print mods }')
        test "$DEP_MODULES" = - || RemoveModules $DEP_MODULES

        # remove module if still loaded
        grep -q "^$MODULE " /proc/modules && \
            $linuxcnc_module_helper remove $MODULE

    done
}


Unload(){

    # shutdown rtapi if it exists

    if CheckRunningProcs rtapi:$MK_INSTANCE; then
	if [ $DEBUG -gt 0 ] ; then
	    halcmd shutdown
	else
	    halcmd shutdown            >/dev/null 2>&1
        fi
    fi

    if test $USE_SHMDRV = no; then

	    # remove any linuxcnc-specific POSIX shm segments if they exist
	    # see src/rtapi/rtapi_shmkeys.h: SHM_FMT

	INSTKEY=`printf 'linuxcnc-%d-' $MK_INSTANCE`
	rm  -f  /dev/shm/${INSTKEY}* >/dev/null 2>&1
    fi


    # wait until rtapi_msgd has vanished. This assures
    # that the last user of the global data segment has vanished,
    # making sure an immediate restart of realtime does not find
    # shm segments still hanging around. The only realistic scenario
    # which could trigger this is runtests.

    # msgd should detect an rtapi_app shutdown, so give some time to
    # let that happen. However, after detecting rtapi_app exit, msgd
    # enters a grace period of 2seconds to collect any remaining messages
    # written to the RT log in the global segment ringbuffer.
    # This helps to debug issues during the critical shutdown phase;
    # if msgd exited right away those messages would be lost.

    # Wait 5s to see if rtapi_msgd exits on its own
    if ! anywait msgd:$MK_INSTANCE 10 ''; then
	# It didn't; get nasty
	echo "ERROR:  msgd:$MK_INSTANCE failed to exit on its own;" \
	     "sending SIGTERM" >&2
	if ! anywait msgd:$MK_INSTANCE 20 TERM; then
	    # ...and get even nastier if SIGTERM fails.  FIXME this needs
	    # to be reviewed; if we get this far, then we might need
	    # operator intervention, including debugging
	    echo "ERROR:  msgd:$MK_INSTANCE failed to exit after SIGTERM;" >&2
	    echo "sending SIGKILL" >&2
	    kill -KILL $(FindRunningProcs msgd:$MK_INSTANCE)
	fi
    fi

    RemoveModules $MODULES_UNLOAD

    # unload shmdrv, if applicable
    if test $BUILD_SYS = 'kbuild' -o $USE_SHMDRV = yes; then
	if CheckShm -l; then
	    # module is loaded; try to force unload, always return success
	    if test $DEBUG -gt 1; then
		echo "realtime unload: notice: attempting to unload shmdrv" >&2
	    fi
	    $linuxcnc_module_helper remove shmdrv 2>/dev/null || true
	else
	    echo -n "realtime unload: warning: " >&2
	    echo "shmdrv expected to be loaded, but wasn't" 1>&2
	fi
    fi
}

CheckUnloaded(){

    # if msgd:$MK_INSTANCE is still around, this might still be a running instance 
    # after all - this applies to all flavors - msgd is always there, so cop out


    if CheckRunningProcs msgd:$MK_INSTANCE; then
	MSGD_PID=`FindRunningProcs msgd:$MK_INSTANCE`
	echo "instance $MK_INSTANCE still running;" \
	     "process msgd:$MK_INSTANCE present (pid $MSGD_PID) !" >&2
	exit 1
    fi

    # if msgd:$MK_INSTANCE isnt running but rtapi:$MK_INSTANCE is, that's bad - msgd
    # should be last to exit
    # this is a noop in kthreads, but clearly an error in uthreads

    if CheckRunningProcs rtapi:$MK_INSTANCE; then
	RTAPI_PID=`FindRunningProcs rtapi:$MK_INSTANCE`
	echo "instance $MK_INSTANCE inproperly shutdown!" >&2
	echo "msgd:$MK_INSTANCE gone," \
	     "but rtapi:$MK_INSTANCE alive (pid $RTAPI_PID)" >&2
	exit 1
    fi

    # regardless if shmdrv is loaded or not, Posix shm segments for this
    # particular instance should not exist and be in use at this point, which 
    # could be the case if a HAL usercomp were still hanging around

    # if any, determine if a process is still using it; complain if so,
    # else remove with a note

    POSIXSHM=`printf '/dev/shm/linuxcnc-%d-*' $MK_INSTANCE`

    for seg in `ls $POSIXSHM 2>/dev/null` ; do
	if pids=`fuser $seg  2>/dev/null` ; then
	    echo "instance $MK_INSTANCE: " \
		 "shared memory $seg still in use by pid: $pids !" >&2
	else
	    echo "instance $MK_INSTANCE: " \
		 "leftover shared memory $seg unused, removing" >&2
	    rm -f $seg
	fi
    done

    # TBD: if fuser /dev/shm indicates a process is using shmdrv, then
    # this is likely another instance, so dont try to unload shmdrv
    # because it's going to fail anyway

    # checks to see if all modules were unloaded

    STATUS=
    for module in $MODULES_UNLOAD ; do
	# check to see if the module is installed
	if $LSMOD | awk '{print $1}' | grep -x $module >/dev/null ; then
	    echo "ERROR: Could not unload '$module'" >&2
	    STATUS=error
	fi
    done
    if [ -n "$STATUS" ] ; then
	exit 1
    fi
}

CMD=$1

case "$CMD" in

  start|load)
	Load || exit $?
	;;
  restart|force-reload)
	Unload
	CheckUnloaded
	Load || exit $?
	;;
  stop|unload)
	Unload || exit $?
	;;
  status)
	CheckStatus || exit $?
	;;
  # for script debugging
  unloadcheck)
	CheckUnloaded
	;;
  # for script debugging
  shmcheck)
	if CheckShm -l; then
	    echo "shmdrv loaded" >&2; exit 0
	fi
  	;;
  *)
	echo "Usage: $0 {start|load|stop|unload|restart|force-reload|status}" >&2
	exit 1
	;;
esac

exit 0

